﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

namespace PexTestsFormatter
{
    public class TestFormatter
    {
        static HashSet<String> usingStmts = new HashSet<String>();
        static List<String> allMethodsToInvoke = new List<String>();

        public static void Format(string[] args)
        {
            try
            {
                String directoryName = args[0];
                processDir(directoryName);

                //Output all classes and methods
                StreamWriter swOutput = new StreamWriter(new FileStream(directoryName + "\\Consolidated.cs", FileMode.Create, FileAccess.Write));

                foreach (String usingStmt in usingStmts)
                {
                    if (usingStmt.Contains("using (PexDisposableContext disposables = PexDisposableContext.Create())"))
                        continue;
                    swOutput.WriteLine(usingStmt);
                }

                if (allMethodsToInvoke.Count <= 40000)
                {
                    swOutput.WriteLine("public class ConsolidatedPexTest");
                    swOutput.WriteLine("{");
                    swOutput.WriteLine("\tpublic static int Main()");
                    swOutput.WriteLine("\t{");
                    foreach (String methodName in allMethodsToInvoke)
                    {
                        swOutput.WriteLine("\t\t try {");
                        swOutput.WriteLine("\t\t\t Console.WriteLine(\"" + methodName + "\");");
                        swOutput.WriteLine("\t\t\t" + methodName);
                        swOutput.WriteLine("\t\t } catch(System.Exception) {}");
                        swOutput.WriteLine("");
                    }

                    swOutput.WriteLine("\t\treturn 0;");
                    swOutput.WriteLine("\t}");
                    swOutput.WriteLine("}");
                    swOutput.Close();
                }
                else
                {
                    //Split the tests into various methods each with 30,000 capacity to avoid compilation errors
                    int totalNumber = allMethodsToInvoke.Count;
                    int numMainMethods = 0;
                    int counter = 0;
                    while (counter < totalNumber)
                    {
                        using (var innerSwOutput = new StreamWriter(new FileStream(directoryName + "\\Consolidated" + numMainMethods + ".cs", FileMode.Create, FileAccess.Write)))
                        {
                            foreach (String usingStmt in usingStmts)
                            {
                                if (usingStmt.Contains("using (PexDisposableContext disposables = PexDisposableContext.Create())"))
                                    continue;
                                innerSwOutput.WriteLine(usingStmt);
                            }

                            innerSwOutput.WriteLine("public class ConsolidatedPexTest" + numMainMethods);
                            innerSwOutput.WriteLine("{");

                            innerSwOutput.WriteLine("\tpublic static int Main" + numMainMethods + "()");
                            innerSwOutput.WriteLine("\t{");

                            for (int localcounter = 0; localcounter < 10000; localcounter++)
                            {
                                if (counter >= totalNumber)
                                    break;

                                var methodName = allMethodsToInvoke[counter++];
                                innerSwOutput.WriteLine("\t\t try {");
                                innerSwOutput.WriteLine("\t\t\t Console.WriteLine(\"" + methodName + "\");");
                                innerSwOutput.WriteLine("\t\t\t" + methodName);
                                innerSwOutput.WriteLine("\t\t } catch(System.Exception) {}");
                                innerSwOutput.WriteLine("");
                            }

                            innerSwOutput.WriteLine("\t\treturn 0;");
                            innerSwOutput.WriteLine("\t}");
                                                      

                            innerSwOutput.WriteLine("}");
                            innerSwOutput.Close();
                        }
                        numMainMethods++;
                    }

                    swOutput.WriteLine("public class ConsolidatedPexTest");
                    swOutput.WriteLine("{");

                    //Dump the number of main methods generated in the complete main method
                    swOutput.WriteLine("\tpublic static int Main()");
                    swOutput.WriteLine("\t{");
                    for (int tcnt = 0; tcnt < numMainMethods; tcnt++)
                    {
                        swOutput.WriteLine("\t\t ConsolidatedPexTest" + tcnt + ".Main" + tcnt + "();");
                    }
                    swOutput.WriteLine("\t\treturn 0;");
                    swOutput.WriteLine("\t}"); 

                    swOutput.WriteLine("}");
                    swOutput.Close();
                }
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex.Message);
            }
        }

        static void processDir(String dirName)
        {
            string[] filePaths = Directory.GetFiles(dirName);

            foreach (string filePathEntry in filePaths)
            {
                if (filePathEntry.Equals(".") || filePathEntry.Equals(".."))
                    continue;

                if (Directory.Exists(filePathEntry))
                    processDir(filePathEntry);

                if (!filePathEntry.EndsWith(".g.cs"))
                    continue;

                StreamReader tr = new StreamReader(filePathEntry);
                String inpStr;
                String namespaceName = "";
                String className = "";
                String methodName = "";

                while ((inpStr = tr.ReadLine()) != null)
                {
                    inpStr = inpStr.Trim();
                    if (inpStr.StartsWith("using"))
                        usingStmts.Add(inpStr);

                    if (inpStr.StartsWith("namespace "))
                    {
                        namespaceName = inpStr.Substring(10, inpStr.Length - 10);
                        usingStmts.Add("using " + namespaceName + ";");
                    }

                    if (inpStr.StartsWith("public partial class "))
                        className = inpStr.Substring(21, inpStr.Length - 21);

                    if (inpStr.StartsWith("public void "))
                    {
                        methodName = inpStr.Substring(12, inpStr.Length - 12);
                        var invokestr = "new " + namespaceName + "." + className + "()." + methodName + ";";
                        if (allMethodsToInvoke.Contains(invokestr))
                        {
                            Console.WriteLine("Error: Duplicate unit test name " + invokestr);
                        } else
                            allMethodsToInvoke.Add(invokestr);
                    }
                }
                tr.Close();
            }

            //Process directories in this
            foreach (String dirEntry in Directory.GetDirectories(dirName))
            {
                processDir(dirEntry);
            }
        }

        /// <summary>
        /// Adds an extension to the classname within the file
        /// </summary>
        /// <param name="filename"></param>
        /// <param name="extension"></param>
        public static void ChangeAndRenameMethodNames(string srcfile, string destfile, string extension)
        {
            //Console.Out.WriteLine("Copying file " + srcfile + " --> " + destfile);
            using (StreamReader tr = new StreamReader(srcfile))
            {
                String inpStr;
                String methodName = "";                

                using (StreamWriter sw = new StreamWriter(destfile))
                {
                    while ((inpStr = tr.ReadLine()) != null)
                    {
                        if (!inpStr.Contains("public void "))
                        {
                            sw.WriteLine(inpStr);
                        }
                        else
                        {
                            inpStr = inpStr.Trim();
                            methodName = inpStr.Substring(12, inpStr.Length - 14);
                            string ninpStr = inpStr.Replace(methodName, methodName + extension);
                            sw.WriteLine("\t\t" + ninpStr);                            
                        }
                    }                    
                }
            }
        }
    }
}
